/* 
 *  Copyright (C) 2006 James Young
 * `gurumind(a)gmail.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections;
using System.Diagnostics;

namespace IPDEditor
{
    public enum IPDElementType { head, record };
    public enum VisualizerType { Editing, Adding };

    public delegate void DataUpdator(object sender, EventArgs e);

    public abstract class IPDItem
    {
        //seed for record handle
        protected static short recSeed;

        //public byte [] orgData;
        public long offset;
        public long length;

        protected Encoding _enc;

        public bool signWriten;

        protected IPDItem parent;
        protected bool _dirt;

        private List<IPDUIElement> _uiElements;
        public List<IPDItem> Items;

        public IPDItem()
        {
            _enc = new ASCIIEncoding();
            _uiElements = new List<IPDUIElement>();
            Items = new List<IPDItem>();
        }
        
        public virtual IPDItem this[int index]
        {
            get
            {
                return Items[index];
            }
        }

        public void Attach(IPDUIElement uiElement)
        {
            _uiElements.Add(uiElement);
        }

        public void Dettach(IPDUIElement uiElement)
        {
            _uiElements.Remove(uiElement);
        }

        public void Notify()
        {
            foreach (IPDUIElement ele in _uiElements)
            {
                ele.Update();
            }
        }

        public bool IsDirt
        {
            get
            {
                return _dirt;
            }
        }

        public virtual short Length
        {
            get
            {
                return 0;
            }
        }

        public virtual bool Read(BinaryReader r)
        {
            offset = r.BaseStream.Position;
            if (offset >= 16885)
            {
                long tmp = offset;
            }
            bool b = _Read(r);
            length = r.BaseStream.Position - offset;
            return b;
        }

        protected virtual bool _Read(BinaryReader r)
        {
            return false;
        }

        public virtual bool Write(BinaryWriter w)
        {
            return _Write(w);
            //Changed = false;
        }

        public virtual bool Write(BinaryWriter w, bool englishOnly)
        {
            return _Write(w, englishOnly);
            //Changed = false;
        }

        protected virtual bool _Write(BinaryWriter w)
        {
            return false;
        }

        protected virtual bool _Write(BinaryWriter w, bool englishOnly)
        {
            return false;
        }
    }

    public class IPDDocument : IPDItem
    {
        //public 
        public IPDHeader head;
        public new List<IPDName> Items;

        public IPDFile fileObject;

        public IPDDocument(IPDFile FileObject)
        {
            parent = null;
            fileObject = FileObject;
            Items = new List<IPDName>();
        }

        public new IPDName this[int index]
        {
            get
            {
                return Items[index];
            }
        }

        protected override bool _Read(BinaryReader _rd)
        {
            head = new IPDHeader(this);
            head.Read(_rd);

            //name blocks
            for (int i = 0; i < head.ItemCount; i++)
            {
                IPDName nameItem = new IPDName(this);
                nameItem.Read(_rd);
                Items.Add(nameItem);
            }

            bool unicode = false;
            for (; ; )
            {
                short pDbId = IPDDatabase.PeekDbId(_rd);
                IPDDatabase db = null;
                if (pDbId >= 0 && pDbId < Items.Count)
                {
                    switch (Items[pDbId].name)
                    {
                        //case "Address Book":
                        //    db = new IPDAddressBook();
                        //    break;
                        case "AutoText":
                            db = new IPDAutoText();
                            break;
                        case "Service Book":
                            db = new IPDServiceBook();
                            break;
                        //case "SMS Messages":
                        //    db = new IPDSMSMessages();
                        //    break;
                        //case "Content Store":
                        //    return true;
                        //case "RMS Databases":
                        //    return true;
                        default:
                            db = new IPDDatabase();
                            break;
                    }
                    db.IsUnicode = unicode;
                    db.Read(_rd);
                    unicode = db.IsUnicode;
                    db.Parent = Items[db.dbId];
                    Items[db.dbId].Items.Add(db);
                }

                //IPDDatabase db = new IPDDatabase();
                //db.IsUnicode = unicode;
                //db.Read(_rd);
                //unicode = db.IsUnicode;
                //if (db.dbId >= 0)
                //{
                //    db.Parent = Items[db.dbId];
                //    Items[db.dbId].Items.Add(db);
                //}

                /// HACK: Debug only code!
                /// 
                if (db == null)
                {
                    break;
                }
                if (db.dbId < head.ItemCount && db.dbId >= 0 && _rd.BaseStream.Position < _rd.BaseStream.Length)
                    continue;
                else
                    break;
            }
            return true;
        }

        protected override bool _Write(BinaryWriter w)
        {
            head.Write(w);

            //name blocks
            foreach(IPDName nameItem in Items)
            {
                nameItem.Write(w);
            }

            recSeed = 1;
            bool usignWriten = false; 
            foreach (IPDName nameItem in Items)
            {
                foreach (IPDDatabase db in nameItem.Items)
                {
                    db.signWriten = usignWriten;
                    if(db.Write(w, true))
                        recSeed++;
                    usignWriten = db.signWriten;                    
                }
                recSeed++;
            }

            foreach (IPDName nameItem in Items)
            {
                foreach (IPDDatabase db in nameItem.Items)
                {
                    db.signWriten = usignWriten;
                    if(db.Write(w, false))
                        recSeed++;
                    usignWriten = db.signWriten;                    
                }
                recSeed++;
            }
            return true;
        }
    }

    public class IPDHeader : IPDItem
    {
        public static string HeadSign = "Inter@ctive Pager Backup/Restore File";
        public byte ver;
        public int ItemCount;

        public IPDHeader(IPDDocument document)
        {
            parent = document;
        }

        protected override bool _Read(BinaryReader r)
        {
            //read head
            byte[] headBytes = r.ReadBytes(HeadSign.Length);
            string headSign = _enc.GetString(headBytes);
            if (headSign != HeadSign)
                throw new Exception("Format Error!");

            //ver, db count, sep
            r.ReadByte();       //0x0A
            ver = r.ReadByte();
            ItemCount = Convert.ToInt32(r.ReadByte() * 256 + r.ReadByte());
            r.ReadByte();       //0x00

            return true;
        }

        protected override bool _Write(BinaryWriter w)
        {
            foreach(char c in HeadSign)
                w.Write(c);
            w.Write((byte)0x0A);
            w.Write((byte)ver);
            w.Write((byte)(ItemCount >> 8));
            w.Write((byte)(ItemCount&byte.MaxValue));
            w.Write((byte)0);

            return true;
        }

        public IPDDocument Parent
        {
            get
            {
                return (IPDDocument)parent;
            }
        }
    }

    public class IPDName : IPDItem
    {
        public short nameLen;
        public string name;
        public new List<IPDDatabase> Items;

        public IPDName(IPDDocument document)
        {
            parent = document;
            Items = new List<IPDDatabase>();
        }

        protected override bool _Read(BinaryReader r)
        {
            nameLen = r.ReadInt16();
            byte[] nameBytes = r.ReadBytes(nameLen - 1);
            name = _enc.GetString(nameBytes).Trim();
            r.ReadByte();       //0x00

            Items = new List<IPDDatabase>();

            return true;
        }

        protected override bool _Write(BinaryWriter w)
        {
            w.Write((byte)(nameLen&byte.MaxValue));
            w.Write((byte)(nameLen >> 8));
            foreach (char c in name)
                w.Write(c);
            w.Write((byte)0);

            return true;
        }

        public new IPDDatabase this[int index]
        {
            get
            {
                return Items[index];
            }
        }

        public IPDDocument Parent
        {
            get
            {
                return (IPDDocument)parent;
            }
        }
    }

    public class IPDDatabase : IPDItem
    {
        public short dbId;
        public int recLen;
        public byte dbver;
        public short recHandle;
        public byte[] uid;
        public new List<IPDField> Items;

        public bool IsUnicode;
        private byte[] dumb;

        public IPDDatabase()
        {
            IsUnicode = false;
            Items = new List<IPDField>();
        }

        public IPDDatabase(IPDName IpdName)
        {
            recHandle = 0;            
            parent = IpdName;
            IsUnicode = false;
            Items = new List<IPDField>();
        }

        public new IPDField this[int index]
        {
            get
            {
                return Items[index];
            }
        }

        public List<IPDField> GetTypeOf(int type)
        {
            List<IPDField> flds = new List<IPDField>();
            foreach (IPDField fld in Items)
                if (fld.type == type)
                    flds.Add(fld);
            return flds;
        }

        public static short PeekDbId(BinaryReader r)
        {
            long pos = r.BaseStream.Position;
            short dbId = r.ReadInt16();
            if (dbId == -1)
            {
                byte[] dumb = r.ReadBytes(4);
                dbId = r.ReadInt16();
            }
            r.BaseStream.Position = pos;
            return dbId;
        }

        protected override bool _Read(BinaryReader r)
        {
            dbId = r.ReadInt16();
            if (dbId == -1)
            {
                dumb = r.ReadBytes(4);
                dbId = r.ReadInt16();
                IsUnicode = true;
            }

            recLen = r.ReadInt32();
            //Trace.Assert(recLen < 128000);

            dbver = r.ReadByte();
            recHandle = r.ReadInt16();
            uid = r.ReadBytes(4);

            while (r.BaseStream.CanRead && r.BaseStream.Position < r.BaseStream.Length && r.BaseStream.Position < offset + recLen + 6)
            {
                IPDField rec = CreateField(IPDField.PeekFieldID(r));
                rec.IsUnicode = IsUnicode;
                rec.Read(r);

                Items.Add(rec);
            }

            return true;
        }

        protected virtual IPDField CreateField(byte type)
        {
            return new IPDField(this);
        }

        protected override bool _Write(BinaryWriter w, bool englishOnly)
        {
            //Trace.Assert(dbId == 0);
            if ((englishOnly && !IsUnicode) || (!englishOnly && IsUnicode))
            {
                if (IsUnicode && !signWriten)
                {
                    w.Write((byte)0xFF);
                    w.Write((byte)0xFF);
                    if (dumb == null)
                    {
                        dumb = new byte[4];
                        dumb[0] = 0x02;
                        dumb[1] = 0x00;
                        dumb[2] = 0x00;
                        dumb[3] = 0x01;
                    }
                    w.Write(dumb);

                    signWriten = true;
                }
                //re-compute recLen

                offset = w.BaseStream.Position;

                recLen = Length;
                w.Write(dbId);
                w.Write(recLen);
                w.Write(dbver);
                //w.Write(recHandle);
                w.Write(recSeed);
                w.Write(uid);
                foreach (IPDField rec in Items)
                {
                    rec.Write(w, englishOnly);
                    //Trace.Assert(w.BaseStream.Position == offset + length);
                }
                return true;
            }
            return false;
        }

        public override short Length
        {
            get
            {
                short len2 = 7;
                foreach (IPDField fld in Items)
                {
                    len2 += fld.Length;
                }
                return len2;
            }
        }

        public virtual string Field2String(IPDField field)
        {
            return EncUtil.BIN2Ascii(field.data);
        }

        public override string ToString()
        {
            return string.Format("{0}", recHandle);
        }

        public virtual string ToLongString()
        {
            return string.Format("recHandle: 0x{0:X4}, uid: 0x{1:x2}{2:x2}{3:x2}{4:x2}, ver {5}, {6} fields.", recHandle, uid[3], uid[2], uid[1], uid[0], dbver, Items.Count);
        }

        public IPDName Parent
        {
            get
            {
                return (IPDName)parent;
            }
            set
            {
                parent = value;
            }
        }
    }
    
    public class IPDField : IPDItem 
    {
        public short len;
        public byte type;
        public byte[] data;
        public bool IsUnicode;

        public List<IPDField> subFields;

        public IPDField(IPDDatabase IpdDatabase)
        {
            parent = IpdDatabase;
            IsUnicode = false;
        }

        public IPDField(IPDDatabase IpdDatabase, byte fldType, int dataLen)
        {
            parent = IpdDatabase;
            type = fldType;
            data = new byte[dataLen];
            IsUnicode = false;
        }

        public static byte PeekFieldID(BinaryReader r)
        {
            long pos = r.BaseStream.Position;
            short len = r.ReadInt16();
            byte type = r.ReadByte(); 
            r.BaseStream.Position = pos;
            return type;
        }

        protected override bool _Read(BinaryReader r)
        {
            len = r.ReadInt16();
            type = r.ReadByte();
            if (len < 0)
	        {
                return false;
	        } 
            data = r.ReadBytes(len);

            return true;
        }

        protected override bool _Write(BinaryWriter w, bool englishOnly)
        {
            if (data == null)
            {
                return false;
            }
            //Trace.Assert(w.BaseStream.Position < 0x166d0);
            //Trace.Assert(!(this is IPDAddressBook));
            //if ((englishOnly && !IsUnicode) || (!englishOnly && IsUnicode))
            //{
                offset = w.BaseStream.Position;
                len = (short)data.Length;
                w.Write(len);
                w.Write(type);
                for (int i = 0; i < data.Length; i++)
                    w.Write((byte)data[i]);

                return true;
            //}
            //return false;
        }

        public override short Length
        {
            get
            {
                if (data != null)
                {
                    return (short)(3 + data.Length);
                }

                return 3;
            }
        }

        public string DataAsString()
        {
            IPDDatabase db = (IPDDatabase)parent;
            return db.Field2String(this);
        }

        public IPDDatabase Parent
        {
            get
            {
                return (IPDDatabase)parent;
            }
        }

        public override string ToString()
        {
            return string.Format("Type: {0}, {1}", type, DataAsString());
        }
    }
}
